﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Diagnostics;

namespace LowLevelGraphics.ColorSpaces
{
    /// <summary>
    /// CIE Lab Color Space
    /// </summary>
    public enum CIELabChannel
    {
        /// <summary>
        /// 
        /// </summary>
        L = 0,
        /// <summary>
        /// 
        /// </summary>
        a = 1,
        /// <summary>
        /// 
        /// </summary>
        b = 2
    }

    /// <summary>
    /// 
    /// </summary>
    [DebuggerDisplay("L: {m_CIEL}, a: {m_CIEa}, b: {m_CIEb}")]
    public class CIELab : AbstractColorSpace
    {
        protected double m_CIEL = 0.0f;
        protected double m_CIEa = 0.0f;
        protected double m_CIEb = 0.0f;

        /// <summary>
        /// primary constructor
        /// </summary>
        /// <param name="_color"></param>
        public CIELab(Color _color)
            : base(_color)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CIELab"/> class.
        /// </summary>
        /// <param name="_L">The _ L.</param>
        /// <param name="_a">The _a.</param>
        /// <param name="_b">The _b.</param>
        public CIELab(double _L, double _a, double _b)
        {
            m_CIEL = _L;
            m_CIEa = _a;
            m_CIEb = _b;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CIELab"/> class.
        /// </summary>
        internal CIELab()
        {
        }

        /// <summary>
        /// used by clone to create a deep copy
        /// </summary>
        /// <param name="_cieLab"></param>
        internal CIELab(CIELab _cieLab)
        {
            this.m_CIEa = _cieLab.m_CIEa;
            this.m_CIEb = _cieLab.m_CIEb;
            this.m_CIEL = _cieLab.m_CIEL;
            this.m_Color = _cieLab.m_Color;
        }

        /// <summary>
        /// Enable casting into operator
        /// </summary>
        /// <param name="_color"></param>
        /// <returns></returns>
        public static explicit operator CIELab(Color _color)
        {
            return new CIELab(_color);
        }

        /// <summary>
        /// Sets or gets l
        /// </summary>
        public double L
        {
            get { return m_CIEL; }
            set { m_CIEL = value; }
        }

        /// <summary>
        /// sets or gets a
        /// </summary>
        public double a
        {
            get { return m_CIEa; }
            set { m_CIEa = value; }
        }

        /// <summary>
        /// sets or gets b
        /// </summary>
        public double b
        {
            get { return m_CIEb; }
            set { m_CIEb = value; }
        }

        /// <summary>
        /// Calculates the values
        /// </summary>
        protected override void CalcValues()
        {
            CIEXYZ xyz = new CIEXYZ(m_Color);
            double X = xyz.X;
            double Y = xyz.Y;
            double Z = xyz.Z;
            double ref_X = 95.047;
            double ref_Y = 100.00;
            double ref_Z = 108.883;
            double var_X = X / ref_X;          //ref_X =  95.047   Observer= 2°, Illuminant= D65
            double var_Y = Y / ref_Y;         //ref_Y = 100.000
            double var_Z = Z / ref_Z;          //ref_Z = 108.883

            if ( var_X > 0.008856 ) var_X = Math.Pow(var_X, ( 1/3 ));
            else                    var_X = ( 7.787 * var_X ) + ( 16 / 116 );
            if ( var_Y > 0.008856 ) var_Y = Math.Pow(var_Y, ( 1/3 ));
            else                    var_Y = ( 7.787 * var_Y ) + ( 16 / 116 );
            if ( var_Z > 0.008856 ) var_Z = Math.Pow(var_Z ,( 1/3 ));
            else                    var_Z = ( 7.787 * var_Z ) + ( 16 / 116 );

            m_CIEL = ( 116 * var_Y ) - 16;
            m_CIEa = 500 * ( var_X - var_Y );
            m_CIEb = 200 * ( var_Y - var_Z );
        }

        /// <summary>
        /// retransform to rgb color space
        /// </summary>
        /// <returns></returns>
        public override Color ToColor()
        {
            double vy = (this.m_CIEL + 16.0f) / 116.0f;
            double vx = this.m_CIEa / 500.0f + vy;
            double vz = vy - this.m_CIEb / 200.0f;

            double vx3 = vx * vx * vx;
            double vy3 = vy * vy * vy;
            double vz3 = vz * vz * vz;

            if (vy3 > 0.008856f)
                vy = vy3;
            else
                vy = (vy - 16.0f / 116.0f) / 7.787f;

            if (vx3 > 0.008856f)
                vx = vx3;
            else
                vx = (vx - 16.0f / 116.0f) / 7.787f;

            if (vz3 > 0.008856f)
                vz = vz3;
            else
                vz = (vz - 16.0f / 116.0f) / 7.787f;

            vx *= 0.95047f; //use white = D65
            vz *= 1.08883f;

            float vr = (float)(vx * 3.2406 + vy * -1.5372 + vz * -0.4986);
            float vg = (float)(vx * -0.9689 + vy * 1.8758 + vz * 0.0415);
            float vb = (float)(vx * 0.0557 + vy * -0.2040 + vz * 1.0570);

            if (vr > 0.0031308f)
                vr = (float)(1.055 * Math.Pow(vr, (1.0 / 2.4)) - 0.055f);
            else
                vr = 12.92f * vr;

            if (vg > 0.0031308f)
                vg = (float)(1.055f * Math.Pow(vg, (1.0 / 2.4f)) - 0.055f);
            else
                vg = 12.92f * vg;

            if (vb > 0.0031308f)
                vb = (float)(1.055f * Math.Pow(vb, (1.0f / 2.4f)) - 0.055f);
            else
                vb = 12.92f * vb;

            return Color.FromArgb((int)vr, (int)vg, (int)vb);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("Color L = {0}, a = {1}, b = {2}", m_CIEL, m_CIEa, m_CIEb);
        }

        /// <summary>
        /// Gets the values.
        /// </summary>
        /// <returns></returns>
        public override object[] GetValues()
        {
            return new object[] { m_CIEL, m_CIEa, m_CIEb };
        }

        /// <summary>
        /// Sets the values.
        /// </summary>
        /// <param name="_aObject">The _a object.</param>
        public override void SetValues(object[] _aObject)
        {
            m_CIEL = (double)_aObject[0];
            m_CIEa = (double)_aObject[1];
            m_CIEb = (double)_aObject[2];
        }

        /// <summary>
        /// use this to create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new CIELab(this);
        }
    }
}
